package com.wzxl.utils.concurrent;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.TimeUnit;

/**
 * 由程序触发的ThreadDump，打印到日志中.
 * <p>
 * 因为ThreadDump本身会造成JVM停顿，所以加上了开关和最少间隔时间的选项(默认不限制)
 * <p>
 * 因为ThreadInfo的toString()最多只会打印8层的StackTrace，所以加上了最大打印层数的选项.(默认为8)
 *
 * @author calvin
 */
public class ThreadDumpper {

    private static final int DEFAULT_MAX_STACK_LEVEL = 8;

    private static Logger logger = LoggerFactory.getLogger(ThreadDumpper.class);

    private boolean enable = true; // 快速关闭该功能
    private long leastIntervalMills = 0; // 每次打印ThreadDump的最小时间间隔，单位为毫秒
    private int maxStackLevel = DEFAULT_MAX_STACK_LEVEL; // 打印StackTrace的最大深度

    private volatile Long lastThreadDumpTime = 0L;

    public ThreadDumpper() {
    }

    public ThreadDumpper(long leastIntervalMills, int maxStackLevel) {
        this.leastIntervalMills = leastIntervalMills;
        this.maxStackLevel = maxStackLevel;
    }

    /**
     * 符合条件则打印线程栈.
     */
    public void threadDumpIfNeed() {
        threadDumpIfNeed(null);
    }

    /**
     * 符合条件则打印线程栈.
     *
     * @param reasonMsg 发生ThreadDump的原因
     */
    public void threadDumpIfNeed(String reasonMsg) {
        if (!enable) {
            return;
        }

        synchronized (this) {
            if (System.currentTimeMillis() - lastThreadDumpTime < leastIntervalMills) {
                return;
            } else {
                lastThreadDumpTime = System.currentTimeMillis();
            }
        }

        logger.info("Thread dump by ThreadDumpper" + (reasonMsg != null ? (" for " + reasonMsg) : ""));

        Map<Thread, StackTraceElement[]> threads = Thread.getAllStackTraces();
        // 两条日志间的时间间隔，是VM被thread dump堵塞的时间.
        logger.info("Finish the threads snapshot");

        StringBuilder sb = new StringBuilder(8192 * 20).append("\n");

        for (Entry<Thread, StackTraceElement[]> entry : threads.entrySet()) {
            dumpThreadInfo(entry.getKey(), entry.getValue(), sb);
        }
        logger.info(sb.toString());

    }

    /**
     * 打印全部的stack，重新实现threadInfo的toString()函数，因为默认最多只打印8层的stack. 同时，不再打印lockedMonitors和lockedSynchronizers.
     */
    private String dumpThreadInfo(Thread thread, StackTraceElement[] stackTrace, StringBuilder sb) {
        sb.append("\"").append(thread.getName()).append("\" Id=").append(thread.getId()).append(' ')
                .append(thread.getState());
        sb.append('\n');
        int i = 0;
        for (; i < Math.min(maxStackLevel, stackTrace.length); i++) {
            StackTraceElement ste = stackTrace[i];
            sb.append("\tat ").append(ste.toString()).append('\n');
        }
        if (i < stackTrace.length) {
            sb.append("\t...").append('\n');
        }

        sb.append('\n');
        return sb.toString();
    }

    /**
     * 快速关闭打印
     */
    public void setEnable(boolean enable) {
        this.enable = enable;
    }

    /**
     * 打印ThreadDump的最小时间间隔，单位为秒，默认为0不限制.
     */
    public void setLeastInterval(int leastIntervalSeconds) {
        synchronized (this) {
            this.leastIntervalMills = TimeUnit.SECONDS.toMillis(leastIntervalSeconds);
        }
    }

    /**
     * 打印StackTrace的最大深度, 默认为8
     */
    public void setMaxStackLevel(int maxStackLevel) {
        this.maxStackLevel = maxStackLevel;
    }
}
